macheteYeti / [CCP] Billing & Inventory Services for Printavo & Online Labels v2

// ==UserScript==
// @name         [CCP] Billing & Inventory Services for Printavo & Online Labels v2
// @namespace    CCP
// @include https://www.printavo.com/*
// @include https://secure.onlinelabels.com/Account/OrderHistory.aspx
// @include https://www.onlinelabels.com/products/*
// @require http://code.jquery.com/jquery-3.4.1.min.js
// @resource customCSS https://fitaf570.com/jqui/ui-lightness.css
// @require https://code.jquery.com/ui/1.12.0/jquery-ui.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
// @downloadURL https://openuserjs.org/install/macheteYeti/[CCP]_Billing_Inventory_Services_for_Printavo_Online_Labels_v2.user.js
// @updateURL https://openuserjs.org/install/macheteYeti/[CCP]_Billing_Inventory_Services_for_Printavo_Online_Labels_v2.user.js
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_setValue
// @license MIT
// @author       macheteYeti
// @description Adds functionality to Printavo & OnlineLabels to enable stock management features.
// @version 40
// ==/UserScript==

$(document).ready(function(){

	user=localStorage.getItem('CCPuser');
	shop=localStorage.getItem('CCPshop');
	
	//determine whether using Online Labels or Printav
	var plat='ol';
	if(window.location.href.indexOf("printavo")>-1)plat='pa';
	
	//specify statuses which should trigger an inventory update
	var include=['Ready for Pickup','Delivery','Ship','In Production'];
	
	//serve different code based on which platform is in use
	switch(plat){
	
		case 'ol':
		
			if(user=='reception')return false;
			if(window.location.href.indexOf('products')>-1){
			
				//if on a product page, determine if we've passed ordering parameters
				var urlParams = new URLSearchParams(window.location.search);
				if(typeof urlParams.get('qty')!=typeof undefined&&urlParams.get('qty')!=null&&urlParams.get('qty').length>0){
				
					//fill in order quantty
					$('.v-pob__custom-quantity-input').val(urlParams.get('qty'));
					setTimeout(()=>{
					
						//add to cart
						$('.v-pob__add-to-cart')[0].click();
						setTimeout(()=>{
						
							//if this is the last item to order, go to cart, otherwise close the page
							if(urlParams.get('last')==null)window.close();
							else window.location.href='https://secure.onlinelabels.com/Cart/ViewCart.aspx';
						},1500);
					},1500);
				}
			}
			else{
			
				//if we're not on a product page, we know we're on the order history page due to distinct @includes
				
				//gather up visible invoices
				var foundInvoices=[];
				$('a[href*=OrderDetails]').each(function(){ foundInvoices.push($(this).attr('href').split('?')[1]); });
				
				//check if we've tracked visible invoices
				$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:{type:'invoices',foundInvoices:foundInvoices},fail:function(jqXHR,textStatus,errorThrown){console.log('Could not get invoices, server response: '+textStatus+': '+errorThrown);},success:function(d){
				
					var tracked='<a class="c-button c-button--secondary c-button--full u-margin-bottom--tiny inventoryTracking tracked">Inventory Tracked!</a>';
					var untracked='<a class="ss-duplicate-order c-button c-button--full u-margin-bottom--tiny inventoryTracking" style="">Track Inventory (Now)</a><a class="ss-duplicate-order c-button c-button--full u-margin-bottom--tiny inventoryTracking threeDays">Track Inventory<br>(In 3 Days)</a>';
					
					$('.p-order-box').each(function(){
					
						//for each order, based on if we find the invoice ID, put out the relevant buttons
						if($.inArray($(this).find('a[href*=OrderDetails]').attr('href').split('?')[1],d.inv)>-1)$(tracked).prependTo($(this).find('.p-order-box__totals>div').first());
						else $(untracked).prependTo($(this).find('.p-order-box__totals>div').first());
					})
				}});
				
				$(document).on('click','.inventoryTracking:not(.tracked)',function(){
				
					//when clicking an inventory button, gather the details
					var orig=$(this);
					var data={'type':'stockChange','action':'add','inv':$(this).parents('.p-order-box').find('a[href*=OrderDetails]').attr('href').split('?')[1]};
					if($(this).hasClass('threeDays'))data['when']='interval';
					else data['when']='now';
					
					//gather the products
					$(this).parents('.p-order-box').find('.u-border--top.u-large-text-align--left').find('.row.align-middle').each(function(){
					
						vals=[$(this).find('div').first().next().find('div').first().next().contents().text().trim().trimLeft()];
						$.each($(this).find('div').first().next().text().replaceAll("\n\n","\n").split("\n"),function(i,v){
						
							if(v.indexOf('Sheets')>-1)vals.push(v.trim().trimLeft().replace("Sheets","").trim().trimLeft());
						});
						data['vals']=vals;
						
						//send the data
						$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:data});
					});
					
					//hide the recording buttons
					$(orig).parent().find('.inventoryTracking').fadeOut();
				});
			}
		break;
		case 'pa':
		
			//if we don't already have a generic inventory utilization button, add it to the top nav
			if($('#genericRecord').length==0)$('.nav.navbar-nav').prepend('<li><a class="menu-item" title="Record Supply Usage" id="genericRecord"><img width="16" height="16" class="new-item__icon" src="https://djqnomyzwkbyb.cloudfront.net/assets/app/icon-new@2x-074c3957e49b442a12334ec6132993212f3e9a0241a52251f6b9832dc17b2e62.png">Record</a></li>');
			
			//retrieve the products we will use in the autocomplete lookup
			sku=$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:{type:'sku'},fail:function(jqXHR,textStatus,errorThrown){console.log('Could not get sku, server response: '+textStatus+': '+errorThrown);}}).responseJSON;
			
			//instantiate the popup to gather the inventory utilization
			$('body').append('<div id="inventoryDialog" style="display:none" title="Log Usage"><form autocomplete="off"><input id="inventoryQty" placeholder="Sheets used" autocomplete="off"><br><input id="inventoryLookup" placeholder="Select a Stock"><input type="hidden" id="inventoryJob"></form></div>');
			$('#inventoryDialog').dialog({autoOpen:false,width:300,position:{my:'right center',at:'right bottom',of: event}});
			//hide the popup initially
			$('#inventoryDialog').dialog('close');
			
			function openDialog(loc=1,e){
			
				/*
					loc 1 = calendar
					loc 2 = generic top nav record
					loc 3 = forced generic top nav record on invoice page load
				*/
				switch(loc){
				
					case 1:
					
						$('.jq-order-preview-modal').css('display','block');
						$('#inventoryDialog').dialog({appendTo:'.jq-order-preview-modal'})
						//set job via url in Printavo popup
						$('#inventoryJob').val($('.modal-content.modal-calendar .btn.btn-success.pull-left').attr('href').split('/')[2]);
					break;
					
					case 2:
					
						//set job via url
						if(window.location.href.indexOf('/invoices/')>-1)$('#inventoryJob').val(window.location.href.split('/').pop());
						$('#inventoryDialog').dialog('open');
					break;
					
					case 3:
					
						//set job via url
						$('#inventoryJob').val(window.location.href.split('/').pop());
					break;
				}
				$('#inventoryDialog').dialog("option","position",{my: "right bottom+15", of: e});
				// if(user!='reception'&&shop!='kingston')$('#inventoryDialog').dialog('open');
				$('#inventoryQty').focus();
			}
			
			//if someone tries to look up a product before specifying a quantity, we add error styles to the quantity box. when someone starts typing in it, they are removed
			$(document).on('keydown','#inventoryQty.ui-state-error',function(){ $(this).removeClass('ui-state-error'); });
			
			$('#inventoryLookup').autocomplete({
			
				source:sku,
				width:200,
				delay:100,
				autoFocus:true,
				focus:function(){$(this).focus();},
				select:function(event,ui){
				
					//when we select a stock from the autocomplete list, check that we have specified a quantity
					if($('#inventoryQty').val().length==0){
					
						$('#inventoryQty').addClass('ui-state-error').effect('shake');
						$('#inventoryLookup').val('');
						$('#inventoryQty').focus();
						return false;
					}
					//if a quantity has been specified, gather the rest of the info
					var dat={type:'stockChange',action:'subtract',val:$('#inventoryQty').val(),id:ui.item.value,user:user,inv:$('#inventoryJob').val()};
					
					// $('#inventoryLookup').val(ui.item.label);
					
					//send the record
					$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:dat,success:function(d){
					
						//once recorded, clear the popup and ready it for another input
						$('#inventoryDialog form')[0].reset();
						$('#inventoryLookup').val('').empty();
						$('#inventoryQty').focus();
					}});
					this.value = "";
					return false;
				}
			});
			
			if(window.location.href.indexOf('/invoices/')>-1||window.location.href.indexOf('/customers/')>-1){
			
				if(window.location.href.indexOf('/invoices/')>-1){
				
					//if we're on an invoice page, store the hashed work order URL
					var wo='';
					$.each($('.dropdown-menu.pull-right a'),function(i,v){
					
						if($(this).attr('href').indexOf('work_orders')>-1)wo=$(this).attr('href').split('/').pop();
					});
					
					$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:{type:'wo',pid:window.location.href.split('/').pop(),wo:wo}});
				}
				
				//if we're on an invoice page, check if we have recorded inventory usage for it
				//if not show the popup
				$.ajax({type:'GET',url:'https://fitaf570.com/ccp/getStock.php',async:false,dataType:'json',data:{type:'checkInvoice',inv:window.location.href.split('/').pop()},success:function(d){
					
					if(!d.inv&&window.location.href.indexOf('/invoices/')>-1)openDialog(3,$('#genericRecord'));
				}});
				
				async function setPaymentIntent(){
				
					//readLoc 1 = individual invoice 2 = all unbilled on /customers/ 3 = partial payment on /invoices/ 4 = partial payment on /customers/
					fetchURL="https://fitaf570.com/ccp/stripe2.php?";
					params={'type':'getIntent','invoices':[],ids:[],'due':0,'user':user,'shop':shop,readLoc:readLoc};
					if(readLoc==1){
					
						var due=1.0*($('.ref-invoice-wrapper.orders-invoice-wrapper.invoice-layout .well.well__no-border').first().find('.form-control.form-control__disabled-bare').last().text().trim().trimLeft().replace('$','').replaceAll(',','')*100);
						if(due==0||due==0.0){
						
							let due=prompt("Could not get invoice total. Please input it here.");
						}
						params.dues=[due];
						params.due+=due;
						params.invoices.push($('h3.pdf-invoice-number').text().split('#')[1].trim());
						params.ids.push(window.location.href.split('/').pop());
					}
					else if(readLoc==3){
					
						params.due=prompt("Enter Partial Payment Amount");
						params.due*=100;
						params.invoices.push($('h3.pdf-invoice-number').text().split('#')[1].trim());
						params.ids.push(window.location.href.split('/').pop());
					}
					else if(readLoc==2){
					
						params.dues=[];
						
						$('#orders-index-list tr').each(function(){
						
							if($(this).find('.paid-label').length>0)return true;
							
							if($.inArray($(this).find('.status').last().text().trim(),noBillStati)==-1){
							
								var thisTot=$(this).find('td').last().prev().find('.text-muted').text().replace('$','').replaceAll(',','')*100;
								params.due+=1.0*thisTot;
								params.dues.push(thisTot);
								params.ids.push($(this).attr('data-clickable').split('/').pop());
								params.invoices.push($(this).find('td').first().clone().children().remove().end().text().trim().trimLeft());
							}
						});
						console.log(params);
					}
					else if(readLoc==4){
					
						params.dues=[];
						
						$('#new_payment tbody tr').each(function(){
						
							if($(this).find('input').val().length>1){
							
								var thisTot=$(this).find('input').val()*100;
								params.due+=1.0*thisTot;
								params.dues.push(thisTot);
								params.ids.push($(this).find('td').first().find('a').attr('href').split('/').pop());
								params.invoices.push($(this).find('td').first().find('a').text().replace('#',''));
							}
						});
						console.log(params);
					}
					$('body').removeClass('loading');
					await fetch(fetchURL+$.param(params),{method:"POST"}).then((response)=>{
					
						if(response.status>=400&&response.status<600){
						
							alert("Stripe servers could not be reached. Please restart the card reader. If the problem persists, contact Yeti");
						}
						return response;
					}).catch((error)=>{
						console.log(error);
					});
				}
				
				function unexpectedDisconnect(){
				
					//should an auto-reconnect be built here?
					alert('Lost connection to card reader. Refresh the page and try again.');
					$('body').removeClass('loading');
				}
				
				$('<li class="dropdown-item item"><a class="serveInvoice" data-type="single"><span class="dollar">$</span> Feed Card Reader</a></li><li class="dropdown-item item"><a class="serveInvoice" data-type="partial"><span class="dollar">$</span> Feed Card Reader (Partial)</a></li><li class="divider"></li>').insertAfter($('.invoice__sidebar-ul .dropdown-menu .divider').eq(($('.invoice__sidebar-ul .dropdown-menu .divider').length-4)));
				if($('.paid-label').length&&window.location.href.indexOf('/invoices/')>-1)$('.serveInvoice').addClass('disabled');
				
				$(document).on('click','.serveInvoice',function(e){
				
					if($(this).hasClass('disabled')){
					
						alert("It appears this invoice is already paid. If you disagree, contact Yeti and provide an invoice ID.");
						return false;
					}
					$('body').addClass('loading');
					console.log('feeding reader based on loc');
					e.preventDefault();
					if($(this).attr('data-type')=='single')readLoc=1;
					else if($(this).attr('id')=='feedSelected')readLoc=4;
					else if($(this).attr('data-type')=='partial')readLoc=3;
					else readLoc=2;
					setPaymentIntent();
				});
				
				if(window.location.href.indexOf('/customers/')>-1){
				
					noBillStati=['Completed','Loss'];
					$('.modal-footer:not(.readered)').append($('<input value="Feed Card Reader (Selected)" class="btn btn-success pull-right serveInvoice" data-type="multi" id="feedSelected">'));
					$('.modal-footer').addClass('readered');
					$(document).arrive('#new_payment',function(){
					
						$('.modal-footer:not(.readered)').append($('<input value="Feed Card Reader (Selected)" class="btn btn-success pull-right serveInvoice" data-type="multi" id="feedSelected">'));
						$('.modal-footer').addClass('readered');
					});
					
					$('<li><a href="#" class="serveInvoice" data-type="multi">Feed Card Reader (<span id="selectedCount">All</span> Unpaid)</a> <span id="selectedTot"></span></li>').insertAfter($('.sidebar-menu li').last());
				}
			}
			$(document).on('click','.dropdown-select-status__menu li',function(e){
			
				//when we change status on the calendar, if we are specifying a qualified status, show the popup
				if($.inArray($(this).text().trim().trimLeft(),include)>-1)openDialog(1,e);
			});
			
			$(document).on('click','#genericRecord',function(e){
			
				//if we click the generic record button in the top nav, show the popup
				openDialog(2,e);
			});
			
			
		break;
		default:break;
	}
});

var newCSS = GM_getResourceText ("customCSS");
GM_addStyle (newCSS);

GM_addStyle( `
	.inventoryTracking{position:absolute;top:0;left:0;width:27%;margin-left:3%}
	.tracked{cursor:default}
	.threeDays{left:30%}
	#inventoryDialog{width:200px;height:200px}
	.ui-dialog.ui-corner-all.ui-widget.ui-widget-content{z-index:999}
	
	#inventoryDialog input{width:150px;margin-bottom:1em}
	
	#inventoryQty.ui-state-error::placeholder{color:#fff}
	
	#genericRecord{cursor:pointer}
	
	.dollar{font:1.4em 'calibri',sans-serif;font-weight:bold;color:#ADB9C0;display:inline-block;margin-right:8px}
	.serveInvoice{cursor:wait}
	
	div[aria-describedby='inventoryDialog']{position:fixed !important}
	.checker,.serveInvoice{cursor:pointer}
	body.loading{cursor:progress}
	body.loading .serveInvoice{cursor:progress !important}
	
	.serveInvoice.disabled{cursor:not-allowed !important}
	
	#feedSelected{width:250px}
` );